/*
 * Copyright © 2018 www.noark.xyz All Rights Reserved.
 *
 * 感谢您选择Noark框架，希望我们的努力能为您提供一个简单、易用、稳定的服务器端框架 ！
 * 除非符合Noark许可协议，否则不得使用该文件，您可以下载许可协议文件：
 *
 *        http://www.noark.xyz/LICENSE
 *
 * 1.未经许可，任何公司及个人不得以任何方式或理由对本框架进行修改、使用和传播;
 * 2.禁止在本项目或任何子项目的基础上发展任何派生版本、修改版本或第三方版本;
 * 3.无论你对源代码做出任何修改和改进，版权都归Noark研发团队所有，我们保留所有权利;
 * 4.凡侵犯Noark版权等知识产权的，必依法追究其法律责任，特此郑重法律声明！
 */
package xyz.noark.orm.accessor.sql.pg;

import xyz.noark.core.util.StringUtils;
import xyz.noark.orm.DataConstant;
import xyz.noark.orm.EntityMapping;
import xyz.noark.orm.FieldMapping;
import xyz.noark.orm.accessor.sql.AbstractSqlExpert;

import java.util.Objects;

/**
 * postgresql
 *
 * @author 小流氓[176543888@qq.com]
 * @since 4.0
 */
public class PgSqlExpert extends AbstractSqlExpert {

    @Override
    protected char wrapCharacter() {
        return '"';
    }

    @Override
    public <T> String genCreateTableSql(EntityMapping<T> em) {
        final StringBuilder sb = new StringBuilder(512);

        // CREATE TABLE table_name (
        sb.append("CREATE TABLE ");
        safeAppend(sb, em.getTableName()).append('(');

        // 创建字段
        for (FieldMapping fm : em.getFieldMapping()) {
            // column1 datatype [constraint],
            sb.append('\n').append(' ').append(' ');
            this.safeAppend(sb, fm.getColumnName());
            sb.append(' ').append(evalFieldType(fm));

            // 主键的 @Id，应该加入唯一性约束
            if (fm.isPrimaryId()) {
                // 自增主键
                if (fm.hasGeneratedValue()) {
                    sb.append(" SERIAL");
                }
                sb.append(" PRIMARY KEY");
            }
            // 普通字段
            else {
                // 是否可以为空的情况
                this.appendNotNull(sb, fm);
                // 默认值的设计
                this.appendDefault(sb, fm);
            }

            // 最后的,号与换行
            sb.append(',');
        }
        sb.deleteCharAt(sb.length() - 1);
        // 结束表字段
        sb.append('\n').append(')').append(';');


        // 为玩家ID字段创建了一个 B-tree 索引 idx_player_id，以便于对该字段进行高效查询
        // CREATE INDEX index_name ON table_name (column_name);
        FieldMapping playerId = em.getPlayerId();
        if (playerId != null && !Objects.equals(playerId, em.getPrimaryId())) {
            // 这里的索引名字为了不重复，需要带上表名
            sb.append("\nCREATE INDEX idx_player_id_on_").append(em.getTableName()).append(" ON ");
            this.safeAppend(sb, em.getTableName()).append(' ').append('(');
            this.safeAppend(sb, playerId.getColumnName()).append(')').append(';');
        }

        // -- 为整个表添加注释
        // COMMENT ON TABLE employee IS '员工信息表';
        if (!StringUtils.isEmpty(em.getTableComment())) {
            sb.append("\nCOMMENT ON TABLE ");
            this.safeAppend(sb, em.getTableName()).append(" IS '").append(em.getTableComment()).append('\'').append(';');
        }

        // --为各个列添加注释
        for (FieldMapping fm : em.getFieldMapping()) {
            this.appendColumnComment(sb, em, fm);
        }
        return sb.toString();
    }

    /**
     * 为SQL语句拼接上字段注释语法部分
     *
     * @param sb SQL语言
     * @param em 实体
     * @param fm 字段
     */
    private void appendColumnComment(StringBuilder sb, EntityMapping<?> em, FieldMapping fm) {
        // 没有注释，忽略
        if (StringUtils.isBlank(fm.getColumnComment())) {
            return;
        }

        // COMMENT ON COLUMN employee.name IS '员工姓名';
        sb.append("\nCOMMENT ON COLUMN ");
        this.safeAppend(sb, em.getTableName()).append('.');
        this.safeAppend(sb, fm.getColumnName());
        sb.append(" IS '").append(fm.getColumnComment()).append('\'').append(';');
    }

    /**
     * 为SQL语句拼接上Default语法部分
     *
     * @param sb SQL语言
     * @param fm 字段
     */
    private void appendDefault(StringBuilder sb, FieldMapping fm) {
        // 如果存在默认的话
        if (fm.hasDefaultValue()) {
            sb.append(" DEFAULT '").append(fm.getDefaultValue()).append('\'');
        }
    }

    /**
     * 为SQL语句拼接上空与非空语法部分
     *
     * @param sb SQL语言
     * @param fm 字段
     */
    private void appendNotNull(StringBuilder sb, FieldMapping fm) {
        // 是否可以为空的情况
        if (fm.isNotNull()) {
            sb.append(" NOT NULL");
        }
    }

    @Override
    protected String evalFieldType(FieldMapping fm) {
        return switch (fm.getType()) {
            // integer (或 int): 常规范围整数，-2147483648 到 2147483647
            case AsInteger, AsAtomicInteger -> "INT";

            // bigint: 大范围整数，-9223372036854775808 到 9223372036854775807
            case AsLong, AsLongAdder, AsAtomicLong -> "BIGINT";

            // 有小数的就直接写上他写的参数
            case AsDouble, AsFloat -> "NUMERIC(" + fm.getPrecision() + "," + fm.getScale() + ")";

            // 日期类型的，三种，其他用不着就不实现啦.
            case AsInstant, AsLocalDateTime, AsDate -> "TIMESTAMP";

            // Boolean直接写死啦，不可能为其他值的.
            case AsBoolean -> "BOOLEAN";

            // 字符串类型的，过长需要换类型
            case AsString, AsJson -> {
                if (fm.getWidth() > DataConstant.TEXT_MAX_WIDTH) {
                    yield "TEXT";
                }
                yield "VARCHAR(" + fm.getWidth() + ")";
                // 如果大于65535，那就要转化为TEXT
                // 其他情况还是使用VarChar方式
            }

            // 其它的参照默认字段规则 ...
            default -> super.evalFieldType(fm);
        };
    }


    @Override
    public <T> String genAddTableColumnSql(EntityMapping<T> em, FieldMapping fm) {
        // ALTER TABLE users ADD COLUMN age INTEGER DEFAULT 25 NOT NULL;
        StringBuilder sb = new StringBuilder(128);
        sb.append("ALTER TABLE ");
        this.safeAppend(sb, em.getTableName());

        sb.append(" ADD COLUMN ");
        this.safeAppend(sb, fm.getColumnName());
        sb.append(' ').append(evalFieldType(fm));

        // 自增主键
        if (fm.hasGeneratedValue()) {
            sb.append(" SERIAL");
        }

        this.appendDefault(sb, fm);
        this.appendNotNull(sb, fm);
        sb.append(';');

        // 注释...
        this.appendColumnComment(sb, em, fm);

        return sb.toString();
    }


    @Override
    public <T> String genUpdateTableColumnSql(EntityMapping<T> em, FieldMapping fm) {
        // ALTER TABLE users ALTER COLUMN age TYPE SMALLINT USING age::SMALLINT;
        StringBuilder sb = new StringBuilder(128);
        sb.append("ALTER TABLE ");
        this.safeAppend(sb, em.getTableName());

        sb.append(" ALTER COLUMN ");
        this.safeAppend(sb, fm.getColumnName());

        String fieldType = evalFieldType(fm);
        sb.append(" TYPE ").append(fieldType);
        sb.append(" USING ");
        this.safeAppend(sb, fm.getColumnName());
        sb.append("::").append(fieldType).append(';');

        // --接着，修改字段以不允许NULL值
        // ALTER TABLE users ALTER COLUMN age SET NOT NULL;
        if (fm.isNotNull()) {
            sb.append("\nALTER TABLE ");
            this.safeAppend(sb, em.getTableName());
            sb.append(" ALTER COLUMN ");
            this.safeAppend(sb, fm.getColumnName());
            sb.append(" SET");
            this.appendNotNull(sb, fm);
            sb.append(';');
        }

        // --最后，设置新的默认值（如果之前的临时默认值不是最终想要的）
        // ALTER TABLE users ALTER COLUMN age DROP DEFAULT;
        // ALTER TABLE users ALTER COLUMN age SET DEFAULT 25;
        if (fm.hasDefaultValue()) {
            sb.append("\nALTER TABLE ");
            this.safeAppend(sb, em.getTableName());
            sb.append(" ALTER COLUMN ");
            this.safeAppend(sb, fm.getColumnName());
            sb.append(" SET");
            this.appendDefault(sb, fm);
            sb.append(';');
        }

        // 注释...
        this.appendColumnComment(sb, em, fm);

        return sb.toString();
    }
}